/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include <journal/models/db/page_table.h>
#include <journal/models/db/common_table.h>

using namespace std;
using namespace journal::model::db;

page_table::page_table()
{
    _thumbnailCells = std::make_shared<std::vector<std::shared_ptr<s::icell>>>();
    _pageLinkedDataCells = std::make_shared<std::vector<std::shared_ptr<s::icell>>>();
    _pagePaperTypeCells = std::make_shared<std::vector<std::shared_ptr<s::icell>>>();
    _pageThumbnailUpToDateCells = std::make_shared<std::vector<std::shared_ptr<s::icell>>>();
    _pagePhotoIdsCells = std::make_shared<std::vector<std::shared_ptr<s::icell>>>();
}

std::shared_ptr<journal::model::db::ipage_table> page_table::create(std::shared_ptr<s::iengine> dbEngine)
{
    std::shared_ptr<journal::model::db::page_table> returnValue = make_shared<page_table>();
    returnValue->_commonTableClient = std::dynamic_pointer_cast<icommon_table_client>(returnValue);
    returnValue->_commonTable = createCommonTable(returnValue->_commonTableClient, dbEngine, L"Page");
    return returnValue;
}

std::shared_ptr<icommon_table> page_table::common()
{
    return _commonTable;
}

void page_table::defineColumns(std::shared_ptr<s::itable> table)
{
    BAJA_LOCK(_mutex);

    _pageIdColumn = s::createColumn<b::uint32>(L"ID", s::column_type::type_int32);
    table->addColumn(_pageIdColumn);

    _pageLinkedDataColumn = s::createColumn<b::uint32>(L"LinkedData", s::column_type::type_int32);
    table->addColumn(_pageLinkedDataColumn);

    _pagePaperTypeColumn = s::createColumn<b::uint32>(L"PaperType", s::column_type::type_int32);
    table->addColumn(_pagePaperTypeColumn);

    _pageThumbnailUpToDateColumn = s::createColumn<b::uint32>(L"ThumbnailUpToDate", s::column_type::type_int32);
    table->addColumn(_pageThumbnailUpToDateColumn);

    _pageThumbnailSizeColumn = s::createColumn<b::uint32>(L"ThumbnailSize", s::column_type::type_int32);
    _pageThumbnailSizeColumn->setIsBlobSize(true);
    table->addColumn(_pageThumbnailSizeColumn);

    _pageThumbnailColumn = s::createBlobColumn(L"Thumbnail", s::column_type::type_binaryBlob);
    _pageThumbnailColumn->setFixed(false);
    table->addColumn(_pageThumbnailColumn);

    _pagePhotoIdsSizeColumn = s::createColumn<b::uint32>(L"PagePhotoIdsSize", s::column_type::type_int32);
    _pagePhotoIdsSizeColumn->setIsBlobSize(true);
    table->addColumn(_pagePhotoIdsSizeColumn);

    _pagePhotoIdsColumn = s::createBlobColumn(L"PagePhotoIds", s::column_type::type_binaryBlob);
    _pagePhotoIdsColumn->setFixed(false);
    table->addColumn(_pagePhotoIdsColumn);
}

void page_table::defineIndexColumns(std::shared_ptr<s::iindex> primaryKeyIndex)
{
    BAJA_LOCK(_mutex);

    _pageIdIndexColumn = s::createIndexColumnDefinition(_pageIdColumn, true);
    primaryKeyIndex->addIndexColumn(_pageIdIndexColumn);
}

void page_table::defineRowCells(std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> row)
{
    BAJA_LOCK(_mutex);

    // Bug 322: TRACK: can we get rid of this 'cell' concept?
    _pageIdCell = _pageIdColumn->createCell(0);
    _pageLinkedDataCell = _pageLinkedDataColumn->createCell(0);
    _pagePaperTypeCell = _pagePaperTypeColumn->createCell(0);
    _pageThumbnailUpToDateCell = _pageThumbnailUpToDateColumn->createCell(0);
    _pageThumbnailSizeCell = _pageThumbnailSizeColumn->createCell(0);
    _pageThumbnailCell = _pageThumbnailColumn->createCellByMoving(s::binary_blob());
    _pagePhotoIdsSizeCell = _pagePhotoIdsSizeColumn->createCell(0);
    _pagePhotoIdsCell = _pagePhotoIdsColumn->createCellByMoving(s::binary_blob());

    row->push_back(_pageIdCell);
    row->push_back(_pageLinkedDataCell);
    row->push_back(_pagePaperTypeCell);
    row->push_back(_pageThumbnailUpToDateCell);
    row->push_back(_pageThumbnailSizeCell);
    row->push_back(_pageThumbnailCell);
    row->push_back(_pagePhotoIdsSizeCell);
    row->push_back(_pagePhotoIdsCell);

    // do not add the thumbnail bits to the default row because they are optional and are not
    // typically set along the other bits.

    _pageLinkedDataCells->push_back(_pageIdCell);
    _pageLinkedDataCells->push_back(_pageLinkedDataCell);

    _pagePaperTypeCells->push_back(_pageIdCell);
    _pagePaperTypeCells->push_back(_pagePaperTypeCell);

    _pageThumbnailUpToDateCells->push_back(_pageIdCell);
    _pageThumbnailUpToDateCells->push_back(_pageThumbnailUpToDateCell);

    _thumbnailCells->push_back(_pageIdCell);
    _thumbnailCells->push_back(_pageThumbnailUpToDateCell);
    _thumbnailCells->push_back(_pageThumbnailSizeCell);
    _thumbnailCells->push_back(_pageThumbnailCell);

    _pagePhotoIdsCells->push_back(_pageIdCell);
    _pagePhotoIdsCells->push_back(_pagePhotoIdsSizeCell);
    _pagePhotoIdsCells->push_back(_pagePhotoIdsCell);
}

void page_table::readIds(
    std::vector<b::uint32>& ids)
{
    BAJA_LOCK(_mutex);

    common()->readIds(nullptr, _pageIdColumn, ids);
}

void page_table::insert(
    const b::uint32& pageId,
    const b::Boolean& linkedData,
    const b::Boolean& thumbnailUpToDate,
    const b::uint32& pagePaperType)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);
    _pageLinkedDataCell->setValue(linkedData ? 1 : 0);
    _pagePaperTypeCell->setValue(pagePaperType);
    _pageThumbnailUpToDateCell->setValue(thumbnailUpToDate ? 1 : 0);
    _pageThumbnailSizeCell->setValue(0);
    _pageThumbnailCell->setValue(s::binary_blob());
    _pagePhotoIdsSizeCell->setValue(0);
    _pagePhotoIdsCell->setValue(s::binary_blob());
    _pagePhotoIdsCell->setAppendOnUpdate(false);

    _commonTable->insert(_commonTable->stagingRow());
}

void page_table::updateLinkedData(
    const b::uint32 pageId,
    const b::Boolean& linkedData)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);
    _pageLinkedDataCell->setValue(linkedData ? 1 : 0);

    _commonTable->update(_pageLinkedDataCells, 1 /* pageId only */);
}

void page_table::updateThumbnail(
    const b::uint32 pageId,
    const b::Boolean& thumbnailUpToDate,
    const s::binary_blob& inBlob)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);
    _pageThumbnailUpToDateCell->setValue(thumbnailUpToDate ? 1 : 0);
    _pageThumbnailSizeCell->setValue(inBlob._size);
    _pageThumbnailCell->setValue(inBlob);

    _commonTable->update(_thumbnailCells, 1 /* pageId only */);
}

void page_table::updatePhotoIds(
    const b::uint32 pageId,
    const std::vector<b::uint32>& pageIds)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);
    if (!pageIds.empty())
    {
        s::binary_blob blob(
            static_cast<b::uint32>(pageIds.size() * sizeof(pageIds[0])),
            &pageIds[0]);
        _pagePhotoIdsCell->setValue(blob);
        _pagePhotoIdsSizeCell->setValue(blob._size);
    }
    else 
    {
        _pagePhotoIdsCell->setValue(s::binary_blob());
        _pagePhotoIdsSizeCell->setValue(0);
    }
    _pagePhotoIdsCell->setAppendOnUpdate(false);

    _commonTable->update(_pagePhotoIdsCells, 1 /* pageId only */);
}

void page_table::load(
    const b::uint32& pageId,
    b::Boolean& pageLinkedData,
    b::uint32& pagePaperType,
    b::Boolean& pageThumbnailUpToDate,
    std::vector<b::uint32>& pagePhotoIds)
{
    BAJA_LOCK(_mutex);

    // select columns...
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(_pageIdColumn);
    result->addColumn(_pageLinkedDataColumn);
    result->addColumn(_pagePaperTypeColumn);
    result->addColumn(_pageThumbnailUpToDateColumn);
    result->addColumn(_pagePhotoIdsSizeColumn);
    result->addColumn(_pagePhotoIdsColumn);
    
    // where table.id = id

    // execute
    read(pageId, result);

    // iterate through the result...
    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL,
        boost::wformat(L">> >> page_table::load() : row count = %i") %
        rows->size()
        );

    BAJA_ASSERT(rows->size() == 1, L"One (and only one) records is expected.");
    s::binary_blob pagePhotoIdsBlob;

    std::vector<std::shared_ptr<s::iresult_row>>::iterator rowIt;
    for (rowIt = rows->begin(); rowIt < rows->end(); ++rowIt)
    {
        // Bug 323 : TRACK: Gmileka: It'd be nice to have an easier way of getting directly to the cell we need...
        std::shared_ptr<s::iresult_row> row = *rowIt;
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();
        std::vector<std::shared_ptr<s::icell>>::iterator cellIt;
        // There should be one cell per row only...
        for (cellIt = cells->begin(); cellIt < cells->end(); ++cellIt)
        {
            std::shared_ptr<s::icell> cell = *cellIt;
            if (cell->getColumnId() == _pageIdColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                if (pageId == typedCell->getValue())
                {
                    continue;
                }
                else
                {
                    BAJA_ASSERT(false, L"The query must return the requested row only!");
                }
            }
            if (cell->getColumnId() == _pageLinkedDataColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                pageLinkedData = typedCell->getValue() == 1 ? true : false;
            }
            if (cell->getColumnId() == _pagePaperTypeColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                pagePaperType = typedCell->getValue();
            }
            if (cell->getColumnId() == _pageThumbnailUpToDateColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                pageThumbnailUpToDate = typedCell->getValue() == 1 ? true : false;
            }
            if (cell->getColumnId() == _pagePhotoIdsColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<s::binary_blob>> typedCell = std::dynamic_pointer_cast<s::icell_typed<s::binary_blob>>(cell);
                typedCell->getValueByMoving(pagePhotoIdsBlob);
                pagePhotoIds.resize(pagePhotoIdsBlob._size / sizeof(pagePhotoIds[0]));
                if (pagePhotoIds.size() > 0)
                {
                    memcpy(&pagePhotoIds[0], pagePhotoIdsBlob._buffer, pagePhotoIdsBlob._size);
                }
            }
        }
    }
}

void page_table::loadThumbnail(
    const b::uint32 pageId,
    b::Boolean& pageThumbnailUpToDate,
    s::binary_blob& outBlob)
{
    BAJA_LOCK(_mutex);

    // select columns...
    std::shared_ptr<s::iresult> result = s::createResult();
    result->addColumn(_pageIdColumn);
    result->addColumn(_pageThumbnailUpToDateColumn);    
    result->addColumn(_pageThumbnailSizeColumn);
    result->addColumn(_pageThumbnailColumn);

    // where table.id = id
    readThumbnail(pageId, result);

    // iterate through the result...
    std::shared_ptr<std::vector<std::shared_ptr<s::iresult_row>>> rows = result->getRows();

    BAJA_ASSERT(rows->size() <= 1, L"One (and only one) records is expected.");

    std::vector<std::shared_ptr<s::iresult_row>>::iterator rowIt;
    for (rowIt = rows->begin(); rowIt < rows->end(); ++rowIt)
    {
        // Bug 323 : TRACK: Gmileka: It'd be nice to have an easier way of getting directly to the cell we need...
        std::shared_ptr<s::iresult_row> row = *rowIt;
        std::shared_ptr<std::vector<std::shared_ptr<s::icell>>> cells = row->getCells();
        std::vector<std::shared_ptr<s::icell>>::iterator cellIt;
        // There should be one cell per row only...
        for (cellIt = cells->begin(); cellIt < cells->end(); ++cellIt)
        {
            std::shared_ptr<s::icell> cell = *cellIt;
            if (cell->getColumnId() == _pageIdColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                if (pageId == typedCell->getValue())
                {
                    continue;
                }
                else
                {
                    BAJA_ASSERT(false, L"The query must return the requested row only!");
                }
            }
            if (cell->getColumnId() == _pageThumbnailUpToDateColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<b::uint32>> typedCell = std::dynamic_pointer_cast<s::icell_typed<b::uint32>>(cell);
                pageThumbnailUpToDate = typedCell->getValue() == 1 ? true : false;
            }
            if (cell->getColumnId() == _pageThumbnailColumn->getId() )
            {
                std::shared_ptr<s::icell_typed<s::binary_blob>> typedCell = std::dynamic_pointer_cast<s::icell_typed<s::binary_blob>>(cell);
                typedCell->getValueByMoving(outBlob);
            }
        }
    }
}

void page_table::read(b::uint32 pageId,
                      std::shared_ptr<s::iresult> result)
{
    _pageIdCell->setValue(pageId);

    _commonTable->read(_commonTable->stagingRow(),
                        static_cast<baja::uint32>(_commonTable->primaryIndex()->getIndexColumns()->size()),
                       result);
}

void page_table::readThumbnail(
    const b::uint32 pageId,
    std::shared_ptr<s::iresult> result)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);

    _commonTable->read(
        _thumbnailCells,
        1 /* pageId only */,
        result);
}

void page_table::dispose(
    b::uint32 pageId)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);

    _commonTable->dispose(_commonTable->stagingRow());
}

void page_table::updatePagePaperType(
    const b::uint32& pageId,
    const b::uint32& pagePaperType)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);
    _pagePaperTypeCell->setValue(pagePaperType);

    _commonTable->update(_pagePaperTypeCells, 1 /* pageId only */);
}

void page_table::updateThumbnailStatus(
    const b::uint32& pageId,
    const b::Boolean& thumbnailUpToDate)
{
    BAJA_LOCK(_mutex);

    _pageIdCell->setValue(pageId);
    _pageThumbnailUpToDateCell->setValue(thumbnailUpToDate ? 1 : 0);

    _commonTable->update(_pageThumbnailUpToDateCells, 1 /* pageId only */);
}

std::shared_ptr<ipage_table> journal::model::db::createPageTable(std::shared_ptr<s::iengine> dbEngine)
{
    return page_table::create(dbEngine);
}